package cc.creativecomputing.resonate.journal;

import cc.creativecomputing.CCApp;
import cc.creativecomputing.CCApplicationManager;
import cc.creativecomputing.control.CCControl;
import cc.creativecomputing.graphics.CCDrawMode;
import cc.creativecomputing.graphics.CCGraphics.CCBlendMode;
import cc.creativecomputing.graphics.CCVBOMesh;
import cc.creativecomputing.graphics.shader.CCGLSLShader;
import cc.creativecomputing.graphics.shader.CCShaderBuffer;
import cc.creativecomputing.graphics.texture.CCTexture2D;
import cc.creativecomputing.graphics.texture.CCTextureIO;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureTarget;
import cc.creativecomputing.io.CCIOUtil;
import cc.creativecomputing.math.CCMath;
import cc.creativecomputing.math.CCVector2f;
import cc.creativecomputing.math.CCVector3f;

/**
 * In this class the velocity shader in introduced and the update shader
 * is extended by constraint checking. Basic constraints in this examples
 * are planes that bounce of the particles.
 * 
 * @author maxg
 *
 */
public class ParticleAppJournal extends CCApp{
	
	private CCShaderBuffer _myDataBuffer;
	private CCShaderBuffer _myLastDataBuffer;
	
	@CCControl(name="emit probability", min=0, max=1)
	private float _cEmitProb = 0;
	
	@CCControl(name="constraint resilience", min=0, max=1)
	private float _cResilience = 0;
	
	@CCControl(name="class 1 amount", min=0f, max=1f)
	private float _cClass1 = 0f;
	
	@CCControl(name="class 2 amount", min=0f, max=1f)
	private float _cClass2 = 0f;
	
	@CCControl(name="class 3 amount", min=0f, max=1f)
	private float _cClass3 = 0f;
	
	@CCControl(name="die prob", min=0f, max=1f)
	private float _cDie = 0f;
	
	@CCControl(name="active")
	private boolean _cActive = false;
	
	private float timePassed = 0;
	private CCVector3f randomVector = new CCVector3f();
	
	
	// fbo to store constraint-planes as pairs of two points
	// p: point on plane, n: normal vector, with p and n in world coordinates
	private CCShaderBuffer _myConstraintData;
	
	// same emitter as in example 01
	private CCGLSLShader   _myEmitShader;
	
	// shader to update the velocities, aka force shader
	private CCGLSLShader   _myVelocityShader;
	
	// this shader is needed to ....
	private CCGLSLShader   _myThruShader;
	
	// update shader to calculate displacement under respect of constraints
	private CCGLSLShader   _myUpdateShader;
	
	// this shader is needed to write the points p and n into the constraint fbo
	private CCGLSLShader   _myInitValueShader;
	private CCTexture2D _mySpriteTexture;
	private CCShaderBuffer _myTargetBuffer;
	
	private MJPointSpriteParticleRenderer _myRenderer;
	
	private int particleW = 100;
	private int particleH = 100;
	private int nConstraints = 6;
	
	private MJTargetFinder _myTargetFinder;
	@Override
	public void setup() {
		
		_mySpriteTexture = new CCTexture2D(CCTextureIO.newTextureData("atlas1x4.png"));
		_myRenderer = new MJPointSpriteParticleRenderer(g, _mySpriteTexture, 4, 4);
		_myTargetFinder = new MJTargetFinder();
		
		addControls("app", "app", this);
		
		_myEmitShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader_journal/emit_rect_fp.glsl"));
		_myEmitShader.load();
		
		_myVelocityShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader_journal/force_fp.glsl"));
		_myVelocityShader.load();
		
		_myThruShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader_journal/thru_fp.glsl"));
		_myThruShader.load();

		_myUpdateShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader_journal/update_constraints_fp.glsl"));
		_myUpdateShader.load();
		
		_myInitValueShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader_journal/init_fp.glsl"));
		_myInitValueShader.load();
		
		_myDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myDataBuffer.clear();
		_myLastDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myLastDataBuffer.clear();
	
		_myConstraintData = new CCShaderBuffer (32, 4, 1, nConstraints, 2, CCTextureTarget.TEXTURE_2D);
		
		addBoxConstraint(new CCVector3f(550,0,0), new CCVector3f(1,0,0), 0);
		addBoxConstraint(new CCVector3f(-550,0,0), new CCVector3f(-1,0,0), 1);
		addBoxConstraint(new CCVector3f(0,450,0), new CCVector3f(0,1,0), 2);
		addBoxConstraint(new CCVector3f(0,-450,0), new CCVector3f(0,-1,0), 3);
		addBoxConstraint(new CCVector3f(0,0,0), new CCVector3f(0,0,1), 4);
		addBoxConstraint(new CCVector3f(0,0,1), new CCVector3f(0,0,1), 5);
		
		_myRenderer.setup(100, 100, _myDataBuffer.attachment(0), _myDataBuffer.attachment(2));
		_myTargetFinder.addNewTexture(CCTextureIO.newTextureData("text03.png"), _cClass1, 100, 100, 1000, 1000);
		
		_myTargetBuffer = new CCShaderBuffer (32, 3, 1, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myTargetBuffer.clear();
		
		_myTargetBuffer.beginDraw();
		_myInitValueShader.start();
		g.beginShape(CCDrawMode.POINTS);
		for (int i=0; i<100; i++) {
			for (int j=0; j<100; j++) {
				//g.textureCoords (10f,0f,1);
				g.textureCoords (_myTargetFinder.getTargets()[i][j].x,_myTargetFinder.getTargets()[i][j].y,1);
				g.vertex(0.5f+i,0.5f+j);
			}
		}
		g.endShape();
		_myInitValueShader.end();
		_myTargetBuffer.endDraw();
	}

	private void addBoxConstraint (CCVector3f pos, CCVector3f norm, int i) {
		
		g.pointSize(1);
		g.noBlend();
		
		_myInitValueShader.start();
		_myConstraintData.beginDraw();
		g.beginShape(CCDrawMode.POINTS);
		
		g.textureCoords (pos.x, pos.y, pos.z);    // point on plane
		g.vertex(0.5f+i,0.5f);
		g.textureCoords (norm.x, norm.y, norm.z);		// normal vector, normalized
		g.vertex(0.5f+i,1.5f);
		
		g.endShape();
		_myConstraintData.endDraw();
		_myInitValueShader.end();
	}
	
	private void swap() {
		CCShaderBuffer tmp = _myDataBuffer;
		_myDataBuffer = _myLastDataBuffer;
		_myLastDataBuffer = tmp;
	}
	
	@Override
	public void update (float theDeltaTime) {
		
		_myEmitShader.start();

		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		
		_myEmitShader.uniform1i("positions", 0);
		_myEmitShader.uniform1i("velocities", 1);
		_myEmitShader.uniform1i("infos", 2);
		_myEmitShader.uniform3f("randomSeed", CCMath.random(100f), CCMath.random(100f),  CCMath.random(3000,10000));
		_myEmitShader.uniform1f("emitProb", _cEmitProb);
		_myEmitShader.uniform1f("dieProb", _cDie);
		_myEmitShader.uniform1f("p0", _cClass1);
		_myEmitShader.uniform1f("p1", _cClass2);
		_myEmitShader.uniform1f("p2", _cClass3);
		
		_myDataBuffer.draw();
		_myEmitShader.end();
		g.noTexture();
		
		swap();
		
		
		// in this shader stage we pass thru all particle data into the actual data buffer
		// because the following velocity shader stage only writes to velocity texture
		// we need to do this step in order to have the other textures before swapping
		_myThruShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		_myThruShader.uniform1i("positions", 0);
		_myThruShader.uniform1i("velocities", 1);
		_myThruShader.uniform1i("infos", 2);
		
		_myDataBuffer.draw();
		_myThruShader.end();
		g.noTexture();
		
		// velocity shader, write only to velocity texture
		if (frameCount%4 == 0) {
			randomVector = new CCVector3f(CCMath.random(100f), CCMath.random(100f),  CCMath.random(3000,10000));
		}
		_myVelocityShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		g.texture (3, _myTargetBuffer.attachment(0));
		_myVelocityShader.uniform1i("positions", 0);
		_myVelocityShader.uniform1i("velocities", 1);
		_myVelocityShader.uniform1i("infos", 2);
		_myVelocityShader.uniform1i("targets", 3);
		_myVelocityShader.uniform3f("randomSeed", randomVector);
		_myVelocityShader.uniform("active", _cActive);
		_myVelocityShader.uniform1f("time", timePassed);
		_myDataBuffer.draw();
		_myVelocityShader.end();
		g.noTexture();
		
		swap();
		
		// update stage with constraints
		_myUpdateShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		g.texture (3, _myConstraintData.attachment(0));
		
		_myUpdateShader.uniform1i("positions", 0);
		_myUpdateShader.uniform1i("velocities", 1);
		_myUpdateShader.uniform1i("infos", 2);
		_myUpdateShader.uniform1i("constraints", 3);
		
		_myUpdateShader.uniform1f("deltaTime", theDeltaTime);
		_myUpdateShader.uniform1i("dataW", _myConstraintData.width());
		_myUpdateShader.uniform1i("dataH", _myConstraintData.height());
		_myUpdateShader.uniform1i("nConstraints", nConstraints);
		_myUpdateShader.uniform1f("resilience", _cResilience);		
		
		_myDataBuffer.draw();
		_myUpdateShader.end();
		g.noTexture();
		
		swap();
		
		timePassed += theDeltaTime;
	}
	
	@Override
	public void draw() {
		_myRenderer.setTime(timePassed);
//		g.clearColor(1f);
		g.clear();
		g.pushMatrix();
		_myRenderer.draw(g);
		g.popMatrix();
		
//		g.image(_myTargetBuffer.attachment(0), 0,0);
	}
	
	public static void main (String[] args) {
		CCApplicationManager myManager = new CCApplicationManager(ParticleAppJournal.class);
		myManager.settings().size(1000, 1000);
		myManager.settings().display(0);
		myManager.settings().antialiasing(8);
		myManager.settings().fov(30);
		myManager.start();
	}
}